// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: daniel

#include "coin2/exchange/upbit/api_util/rate_tracker.h"

#include <sstream>
#include <string>

#include <boost/algorithm/string/replace.hpp>
#include <glog/logging.h>

#include "coin2/base/proto_util.h"
#include "coin2/exchange/base/order/rate_config.h"
#include "coin2/exchange/order/connection.pb.h"

namespace coin2::exchange::upbit::api_util {

constexpr auto EXAMPLE_SPOT_UPBIT = R"(
"connection_config": {
  "key_filepath": "/home/ec2-user/config/trade/xunke06/upbit/trade_key.json",
  "rate_limit_info": {
    "spot_upbit": {
      "all_sec": {
        "secs": 1,
        "reqs": 30
      },
      "all_min": {
        "secs": 60,
        "reqs": 900
      },
      "ord_sec": {
        "secs": 1,
        "reqs": 8
      },
      "ord_min": {
        "secs": 60,
        "reqs": 200
      }
    }
  }
})";

RateTracker::RateTracker(const MarketExchangeApi& mea, const NativeClientConfig& config)
    : default_rate_tracker_{nullptr},
      order_rate_tracker_{nullptr},
      rate_limit_report_writer_{nullptr} {
  using RateConfig = coin2::exchange::base::order::RateConfig;
  coin2::exchange::order::RateLimitInfoSpotUpbit rate_config;
  RateConfig::Get()->GetConfig(mea, config, &rate_config);
  if (!rate_config.has_all_sec() || !rate_config.has_all_min() || !rate_config.has_ord_sec() ||
      !rate_config.has_ord_min()) {
    RateConfig::CheckFail(mea.String(), EXAMPLE_SPOT_UPBIT);
  }
  const auto& all_sec = rate_config.all_sec();
  const auto& all_min = rate_config.all_min();
  const auto& ord_sec = rate_config.ord_sec();
  const auto& ord_min = rate_config.ord_min();
  LOG(INFO) << "\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
            << "\nOG request rate was set as below:\n"
            << "\nall_sec: [" << all_sec.reqs() << " (reqs) / " << all_sec.secs() << " (seconds)]"
            << "\nall_min: [" << all_min.reqs() << " (reqs) / " << all_min.secs() << " (seconds)]"
            << "\nord_sec: [" << ord_sec.reqs() << " (reqs) / " << ord_sec.secs() << " (seconds)]"
            << "\nord_min: [" << ord_min.reqs() << " (reqs) / " << ord_min.secs() << " (seconds)]"
            << "\n\nOr you can configure customized rate in driver config. For example: "
            << EXAMPLE_SPOT_UPBIT
            << "\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~";
  const int64_t nanosecond = 1'000'000'000LL;
  default_rate_tracker_ = std::make_unique<UpbitRateTracker>(
      all_sec.secs() * nanosecond,
      all_sec.reqs(),
      all_min.secs() * nanosecond,
      all_min.reqs());
  order_rate_tracker_ = std::make_unique<UpbitRateTracker>(
      ord_sec.secs() * nanosecond,
      ord_sec.reqs(),
      ord_min.secs() * nanosecond,
      ord_min.reqs());
  // rate limit report
  rate_limit_report_writer_ = std::make_unique<RateLimitReportWriter>();
  rate_limit_report_writer_->SetRateLimitInfoString(&rate_config);
}

bool RateTracker::HasEnoughRateLimit(int64_t timestamp, ActionType action_type) {
  bool ret = true;
  if (action_type == ActionType::PLACE || action_type == ActionType::CANCEL) {
    ret = order_rate_tracker_->HasEnoughRateLimit(timestamp);
  } else {
    ret = default_rate_tracker_->HasEnoughRateLimit(timestamp);
  }
  if (!ret) {
    rate_limit_report_writer_->SetHitRateLimit(timestamp, action_type);
  }
  return ret;
}

void RateTracker::SetRateLimitInfo(int32_t rate_limit, int32_t rate_limit_remaining) {
  // TODO(daniel)
}

void RateTracker::Push(int32_t num_reqs, int64_t timestamp, ActionType action_type) {
  if (action_type == ActionType::PLACE || action_type == ActionType::CANCEL) {
    order_rate_tracker_->Push(num_reqs, timestamp);
  } else {
    default_rate_tracker_->Push(num_reqs, timestamp);
  }
}

void RateTracker::ParseRateLimitInfo(
    const std::shared_ptr<AsioHttpContext>& http_context,
    ActionType action_type) {
  // TODO(daniel)
  if (!http_context) {
    return;
  }

  // const auto& header = http_context->res.base();
  // if (header.count("Remaining-Req") == 0) {
  //   return;
  // }
  // std::stringstream ss;
  // ss << header.at("Remaining-Req");
  // std::string group_str;
  // std::string remain_min_str;
  // std::string remain_sec_str;
  // std::getline(ss, group_str, ';');
  // std::getline(ss, remain_min_str, ';');
  // std::getline(ss, remain_sec_str, ';');
  // boost::replace_all(group_str, "group=", "");
  // boost::replace_all(remain_sec_str, "sec=", "");
  // boost::replace_all(remain_min_str, "min=", "");
  // int64_t rate_limit_remain_sec = stoll(remain_sec_str);
  // int64_t rate_limit_remain_min = stoll(remain_min_str);
  // if (group_str == "default") {
  //   default_rate_tracker_->UpdateRateLimitInfo(rate_limit_remain_sec, rate_limit_remain_min);
  // } else if (group_str == "order") {
  //   order_rate_tracker_->UpdateRateLimitInfo(rate_limit_remain_sec, rate_limit_remain_min);
  // } else {
  //   // do nothing
  // }
}

void RateTracker::SetRateLimitInfoString(const google::protobuf::Message* message) {
  rate_limit_report_writer_->SetRateLimitInfoString(message);
}

void RateTracker::WriteRateLimitReportMessage(OrderLoggerCB callback) {
  if (!callback) {
    return;
  }
  rate_limit_report_writer_->Write(callback);
}

double RateTracker::GetMaxRateUsedRatio(int64_t timestamp, ActionType action_type) {
  switch (action_type) {
    case ActionType::PLACE:
    case ActionType::CANCEL:
    case ActionType::AMEND: {
      if (!order_rate_tracker_) {
        return 0.;
      }
      return order_rate_tracker_->GetMaxRateUsedRatio(timestamp);
    }
    case ActionType::QUERY: {
      if (!default_rate_tracker_) {
        return 0.;
      }
      return default_rate_tracker_->GetMaxRateUsedRatio(timestamp);
    }
    default: {
      // shouldn't reached here
      LOG(ERROR) << "ops~ something is wrong, action_type=" << action_type;
      return 0.;
    }
  }
}

}  // namespace coin2::exchange::upbit::api_util
